package com.cms.service.impl;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.hibernate.transform.Transformers;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.cms.dto.weixin.Matchrule;
import com.cms.dto.weixin.MsgRequest;
import com.cms.entity.weixin.Account;
import com.cms.entity.weixin.AccountFans;
import com.cms.entity.weixin.AccountMenu;
import com.cms.entity.weixin.MsgNews;
import com.cms.entity.weixin.MsgText;
import com.cms.service.WeiXinService;
import com.cms.support.weixinArgs.HttpMethod;
import com.cms.support.weixinArgs.MpAccount;
import com.cms.support.weixinArgs.MsgType;
import com.cms.support.weixinArgs.MsgXmlUtil;
import com.cms.support.weixinArgs.WxApi;
import com.cms.support.weixinArgs.WxApiClient;
import com.cms.support.weixinArgs.WxMessageBuilder;

/**
 * 业务消息处理 开发者根据自己的业务自行处理消息的接收与回复；
 */

@Service
public class WeiXinServiceImpl extends BaseServiceImpl implements WeiXinService {
	
	private final Logger logger = Logger.getLogger(this.getClass());

	/**
	 * 处理消息 开发者可以根据用户发送的消息和自己的业务，自行返回合适的消息；
	 * 
	 * @param msgRequest
	 *            : 接收到的消息
	 * @param appId
	 *            ： appId
	 * @param appSecret
	 *            : appSecret
	 */
	@Override
	public String processMsg(MsgRequest msgRequest, MpAccount mpAccount) {
		String msgtype = msgRequest.getMsgType();// 接收到的消息类型
		String respXml = null;// 返回的内容；
		if (msgtype.equals(MsgType.Text.toString())) {
			/**
			 * 文本消息，一般公众号接收到的都是此类型消息
			 */
			respXml = this.processTextMsg(msgRequest, mpAccount);
		} else if (msgtype.equals(MsgType.Event.toString())) {// 事件消息
			/**
			 * 用户订阅公众账号、点击菜单按钮的时候，会触发事件消息
			 */
			respXml = this.processEventMsg(msgRequest, mpAccount.getAccount());

			// 其他消息类型，开发者自行处理
		} else if (msgtype.equals(MsgType.Image.toString())) {// 图片消息

		} else if (msgtype.equals(MsgType.Location.toString())) {// 地理位置消息

		}

		// 如果没有对应的消息，默认返回订阅消息；
		if (StringUtils.isEmpty(respXml)) {
			MsgText text = baseDao.getByProperty(MsgText.class, "inputcode",
					MsgType.SUBSCRIBE.toString());
			if (text != null) {
				respXml = MsgXmlUtil.textToXml(WxMessageBuilder
						.getMsgResponseText(msgRequest, text));
			}
		}
		return respXml;
	}

	@Cacheable(value = "wxCache")
	@Override
	public MpAccount getMpAccount(String account) {
		Account entity = baseDao.getByProperty(Account.class, "account",
				account);
		return Account2MpAccount(entity);

	}

	/**
	 * @Title: Account2MpAccount
	 * @Description: TODO(转换)
	 * @param @param entity
	 * @param @return 设定文件
	 * @return MpAccount 返回类型
	 * @throws
	 */
	private MpAccount Account2MpAccount(Account entity) {
		MpAccount mpAccount = new MpAccount();
		if (entity != null) {
			mpAccount.setAccount(entity.getAccount());
			mpAccount.setAppid(entity.getAppid());
			mpAccount.setAppsecret(entity.getAppsecret());
			mpAccount.setUrl(entity.getUrl());
			mpAccount.setToken(entity.getToken());
			mpAccount.setMsgcount(entity.getMsgcount());
		}
		return mpAccount;
	}

	// 处理文本消息
	private String processTextMsg(MsgRequest msgRequest, MpAccount mpAccount) {
		String content = msgRequest.getContent();
		if (!StringUtils.isEmpty(content)) {// 文本消息
			List<Object> any = new ArrayList<Object>();
			any.add(Transformers.aliasToBean(MsgNews.class));
			any.add(Restrictions.eq("inputcode", content.trim()));
			any.add(Restrictions.eq("wxAccount", mpAccount.getAccount()));
			List<MsgNews> msgNews = baseDao.findByAny(MsgNews.class, 0,
					mpAccount.getAccount(), any.toArray());
			if (!CollectionUtils.isEmpty(msgNews)) {
				return MsgXmlUtil.newsToXml(WxMessageBuilder
						.getMsgResponseNews(msgRequest, msgNews));
			}
		}
		return null;
	}

	// 处理事件消息
	private String processEventMsg(MsgRequest msgRequest, String wxAccount) {
		if (MsgType.SUBSCRIBE.toString().equals(msgRequest.getEvent())) {// 订阅消息
			List<Object> any = new ArrayList<Object>();
			any.add(Transformers.aliasToBean(MsgText.class));
			any.add(Restrictions.eq("inputcode", MsgType.SUBSCRIBE.toString()));
			any.add(Restrictions.eq("wxAccount", wxAccount));
			MsgText text = baseDao.getByAny(MsgNews.class, any.toArray());
			if (text != null) {
				return MsgXmlUtil.textToXml(WxMessageBuilder
						.getMsgResponseText(msgRequest, text));
			}
		} else if (MsgType.UNSUBSCRIBE.toString().equals(msgRequest.getEvent())) {// 取消订阅消息
			List<Object> any = new ArrayList<Object>();
			any.add(Transformers.aliasToBean(MsgText.class));
			any.add(Restrictions.eq("inputcode", MsgType.UNSUBSCRIBE.toString()));
			any.add(Restrictions.eq("wxAccount", wxAccount));
			MsgText text = baseDao.getByAny(MsgNews.class, any.toArray());
			if (text != null) {
				return MsgXmlUtil.textToXml(WxMessageBuilder
						.getMsgResponseText(msgRequest, text));
			}
		} else {// 点击事件消息
			String key = msgRequest.getEventKey();
			if (!StringUtils.isEmpty(msgRequest.getEventKey())) {
				/**
				 * 固定消息 _fix_ ：在我们创建菜单的时候，做了限制，对应的event_key 加了 _fix_
				 * 
				 * 当然开发者也可以进行修改
				 */

				if (key.startsWith("_fix_")) {
					String url = key.substring("_fix_".length());
					System.out.println(url);
				}
			}

		}
		return null;
	}

	/*
	 * <p>Description: 发布菜单</p>
	 */
	@Override
	public JSONObject publishMenu(String account, MpAccount mpAccount) {
		logger.info("------------account----------:"+account);
		List<AccountMenu> menus = baseDao.findByProperty(AccountMenu.class,
				"wxAccount", account);
		logger.info("------------menus----------:"+menus);

		Matchrule matchrule = new Matchrule();
		String menuJson = prepareMenus(menus, matchrule);
		logger.info("------------menuJson----------:"+menuJson);
		JSONObject rstObj = WxApiClient.publishMenus(menuJson, mpAccount);// 创建普通菜单

		// 以下为创建个性化菜单demo，只为男创建菜单；其他个性化需求 设置 Matchrule 属性即可
		// matchrule.setSex("1");//1-男 ；2-女
		// JSONObject rstObj = WxApiClient.publishAddconditionalMenus(menuJson,
		// mpAccount);// 创建个性化菜单

		if (rstObj != null) {// 成功，更新菜单组
			if (rstObj.containsKey("menu_id")) {
				baseDao.sqlUpdate(
						"UPDATE t_weixin_menu SET status = 0 where wxAccount=?",
						mpAccount.getAccount());
			} else if (rstObj.containsKey("errcode")
					&& rstObj.getIntValue("errcode") == 0) {
				baseDao.sqlUpdate(
						"UPDATE t_weixin_menu SET status = 0 where wxAccount=?",
						mpAccount.getAccount());
			}
		}
		return rstObj;
	}

	// 删除菜单
	public JSONObject deleteMenu(MpAccount mpAccount) {
		JSONObject rstObj = WxApiClient.deleteMenu(mpAccount);
		if (rstObj != null && rstObj.getIntValue("errcode") == 0) {// 成功，更新菜单组
			baseDao.sqlUpdate(
					"UPDATE t_weixin_menu SET status = 0 where wxAccount=?",
					mpAccount.getAccount());
		}
		return rstObj;
	}

	// 获取用户列表
	public boolean syncAccountFansList(MpAccount mpAccount) {
		String nextOpenId = null;
		// 查询结果
		List<Object> any = new ArrayList<Object>();
		any.add(Transformers.aliasToBean(AccountFans.class));
		any.add(Order.desc("id"));
		List<AccountFans> list = baseDao.findByAny(AccountFans.class, 0, 1,
				any.toArray());
		AccountFans lastFans = list.get(0);
		if (lastFans != null) {
			nextOpenId = lastFans.getOpenId();
		}
		return doSyncAccountFansList(nextOpenId, mpAccount);
	}

	// 同步粉丝列表(开发者在这里可以使用递归处理)
	private boolean doSyncAccountFansList(String nextOpenId, MpAccount mpAccount) {
		String url = WxApi.getFansListUrl(
				WxApiClient.getAccessToken(mpAccount), nextOpenId);
		JSONObject jsonObject = WxApi.httpsRequest(url, HttpMethod.POST, null);
		if (jsonObject.containsKey("errcode")) {
			return false;
		}
		List<AccountFans> fansList = new ArrayList<AccountFans>();
		if (jsonObject.containsKey("data")) {
			if (jsonObject.getJSONObject("data").containsKey("openid")) {
				JSONArray openidArr = jsonObject.getJSONObject("data")
						.getJSONArray("openid");
				int length = 5;// 同步5个
				if (openidArr.size() < length) {
					length = openidArr.size();
				}
				for (int i = 0; i < length; i++) {
					Object openId = openidArr.get(i);
					AccountFans fans = WxApiClient.syncAccountFans(
							openId.toString(), mpAccount);
					fansList.add(fans);
				}
				// 批处理
				for (AccountFans accountFans : fansList) {
					baseDao.save(accountFans);
				}
			}
		}
		return true;
	}

	// 获取用户信息接口 - 必须是开通了认证服务，否则微信平台没有开放此功能
	public AccountFans syncAccountFans(String openId, MpAccount mpAccount,
			boolean merge) {
		AccountFans fans = WxApiClient.syncAccountFans(openId, mpAccount);
		if (merge && null != fans) {
			AccountFans tmpFans = baseDao.getByProperty(AccountFans.class,
					"openId", openId);
			if (tmpFans == null) {
				baseDao.save(fans);
			} else {
				fans.setId(tmpFans.getId());
				baseDao.update(fans);
			}
		}
		return fans;
	}

	// 根据openid 获取粉丝，如果没有，同步粉丝
	public AccountFans getFansByOpenId(String openId, MpAccount mpAccount) {
		AccountFans fans = baseDao.getByProperty(AccountFans.class, "openId",
				openId);
		if (fans == null) {// 如果没有，添加
			fans = WxApiClient.syncAccountFans(openId, mpAccount);
			if (null != fans) {
				baseDao.save(fans);
			}
		}
		return fans;
	}

	/**
	 * 获取微信公众账号的菜单
	 * 
	 * @param menus
	 *            菜单列表
	 * @param matchrule
	 *            个性化菜单配置
	 * @return
	 */
	private String prepareMenus(List<AccountMenu> menus, Matchrule matchrule) {
		if (!CollectionUtils.isEmpty(menus)) {
			List<AccountMenu> parentAM = new ArrayList<AccountMenu>();
			Map<Long, List<JSONObject>> subAm = new HashMap<Long, List<JSONObject>>();
			for (AccountMenu m : menus) {
				if (m.getParentId() == 0L) {// 一级菜单
					parentAM.add(m);
				} else {// 二级菜单
					if (subAm.get(m.getParentId()) == null) {
						subAm.put(m.getParentId(), new ArrayList<JSONObject>());
					}
					List<JSONObject> tmpMenus = subAm.get(m.getParentId());
					tmpMenus.add(getMenuJSONObj(m));
					subAm.put(m.getParentId(), tmpMenus);
				}
			}
			JSONArray arr = new JSONArray();
			for (AccountMenu m : parentAM) {
				if (subAm.get(m.getId()) != null) {// 有子菜单
					arr.add(getParentMenuJSONObj(m, subAm.get(m.getId())));
				} else {// 没有子菜单
					arr.add(getMenuJSONObj(m));
				}
			}
			JSONObject root = new JSONObject();
			root.put("button", arr);
			root.put("matchrule", JSONObject.toJSON(matchrule));
			return JSONObject.toJSONString(root);
		}
		return "error";
	}

	/**
	 * 此方法是构建菜单对象的；构建菜单时，对于 key 的值可以任意定义； 当用户点击菜单时，会把key传递回来；对已处理就可以了
	 * 
	 * @param menu
	 * @return
	 */
	private JSONObject getMenuJSONObj(AccountMenu menu) {
		JSONObject obj = new JSONObject();
		obj.put("name", menu.getName());
		obj.put("type", menu.getMtype());
		if ("click".equals(menu.getMtype())) {// 事件菜单
			if ("fix".equals(menu.getEventType())) {// fix 消息
				obj.put("key", "_fix_" + menu.getMsgId());// 以 _fix_ 开头
			} else {
				if (StringUtils.isEmpty(menu.getInputCode())) {// 如果inputcode为空，默认设置为subscribe，以免创建菜单失败
					obj.put("key", "subscribe");
				} else {
					obj.put("key", menu.getInputCode());
				}
			}
		} else {// 链接菜单-view
			obj.put("url", menu.getUrl());
		}
		return obj;
	}

	private JSONObject getParentMenuJSONObj(AccountMenu menu,
			List<JSONObject> subMenu) {
		JSONObject obj = new JSONObject();
		obj.put("name", menu.getName());
		obj.put("sub_button", subMenu);
		return obj;
	}

}
